/* findbox.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "swis.h"

#include "event.h"
#include "gadgets.h"

#include "err.h"
#include "fade.h"
#include "findbox.h"
#include "findexp.h"
#include "idle.h"
#include "log.h"
#include "msgtrans.h"
#include "nbgroups.h"
#include "nfstr.h"
#include "newsbase.h"
#include "newsfind.h"
#include "nfchoices.h"
#include "nfstatus.h"
#include "nftcodes.h"

enum FindBoxGadgets {
  FB_Group,
  FB_Header = 5,
  FB_Expression = 0xb,
  FB_Search = 0xd,
  FB_CaseSensve = 0xf,
  FB_Strict,
  FB_Reverse,
  FB_Delete = 0x15
};

static char zak[256];

static newsfind_groupinfo findbox_groupinfo;
static newsfind_groupinfo findbox_searchgroups;
static ObjectId findbox_id = NULL_ObjectId;

static int findbox_firstgroup;

static int findbox_clickedselect;

static int findbox_abortsearch(int, ToolboxEvent *, IdBlock *, void *);
static int findbox_cancelsearch(int, ToolboxEvent *, IdBlock *, void *);
static int findbox_nextgroup(int, WimpPollBlock *, IdBlock *, void *);
static int findbox_searchoverview(int c, WimpPollBlock *e, IdBlock *i, void *h);

static int findbox_states(int c, ToolboxEvent *e, IdBlock *i, void *h)
{
  int selected, head_state;
  E(radiobutton_get_state(0, findbox_id, FB_SearchHeader,
  	&head_state, &selected));
  if (head_state)
    E(gadget_unfade(0, findbox_id, FB_Header));
  else
    E(gadget_fade(0, findbox_id, FB_Header));
  return 1;
}

static void findbox_listbuilt(newsfind_groupinfo *groupinfo)
{
  if (findbox_id)
    E(stringset_set_available(0, findbox_id, FB_Group, groupinfo->groups));
  if (newsfind_info.status == NotReady)
  {
    newsfind_info.status = Idle;
    if (findbox_id)
    {
      E(gadget_unfade(0, findbox_id, FB_Group));
      E(gadget_unfade(0, findbox_id, FB_Search));
      findbox_states(0, 0, 0, 0);
    }
  }
}

void findbox_notready()
{
  if (newsfind_info.status == Busy)
    findbox_abortsearch(NFT_AbortSearch, 0, 0, 0);
  newsfind_info.status = NotReady;
  if (findbox_id)
  {
    E(gadget_fade(0, findbox_id, FB_Group));
    E(gadget_fade(0, findbox_id, FB_Search));
  }
}

void findbox_getgroups()
{
  if (findbox_id == NULL_ObjectId || !newsbase_isready())
    return;
/*
if (findbox_id == NULL_ObjectId)
{
fprintf(stderr, "Can't get groups because findbox_id is null\n");
return;
}
if (!newsbase_isready())
{
fprintf(stderr, "Can't get groups because Newsbase isn't ready\n");
return;
}
*/
  nbgroups_initialise(&findbox_groupinfo, "*");
}

static int findbox_update(WimpMessage *m, void *h)
{
  switch (m->data.words[0])
  {
    case NewsBase_GroupCreated:
      newsfind_addgroup(&findbox_groupinfo, &m->data.bytes[12]);
      findbox_listbuilt(&findbox_groupinfo);
      return 1;
    case NewsBase_GroupDeleted:
      newsfind_delgroup(&findbox_groupinfo, &m->data.bytes[12]);
      findbox_listbuilt(&findbox_groupinfo);
      return 1;
  }
  return 0;
}

static int findbox_getarticle(int c, WimpPollBlock *e, IdBlock *i, void *h)
{
  NewsBaseGetArticle nbga;
  idle_off();
  nfstatus_setmessage(++newsfind_info.message, newsfind_info.messages);
  nbga.article = newsfind_info.article_no;
  nbga.flags = 0;
  strcpycomma(nbga.groupname, newsfind_info.group);
  newsbase_send(NewsBase_GetArticle, &nbga);
  return 1;
}

static WimpEventHandler *found_idler;
static int del_article;
static char del_group[128];

static void findbox_deletearticle(int article, const char *groupname)
{
  NewsBaseDeleteArticle nbda;
  nbda.first = nbda.last = article;
  strcpycomma(nbda.groupname, groupname);
  newsbase_send(NewsBase_DeleteArticle, &nbda);
/*sprintf(zak, "Deleting article %d of group %s\n", article, nbda.groupname);
log_log(zak);*/
}

static void findbox_found(unsigned article, WimpEventHandler *idler)
{
  nfstatus_setfound(++newsfind_info.found);
  log_found(article);
  found_idler = idler;
  del_article = article;
  strcpycomma(del_group, newsfind_info.group);
  if (nfchoices.copy=='Y' || nfchoices.copy=='y')
  {
    NewsBaseCopyArticle nbca;
    nbca.first = nbca.last = article;
    strcpy(nbca.groupandfolder, del_group);
    strcpy(nbca.groupandfolder + strlen(nbca.groupandfolder)+1,
    	nfchoices.foundfolder);
    newsbase_send(NewsBase_CopyArticle, &nbca);
  }
  else if (newsfind_info.del)
    findbox_deletearticle(article, del_group);
  else
    idle_on(idler, 0);
}

static void findbox_nextarticle_forwards()
{
  int hdr;
  while (newsfind_info.overview_ix < newsfind_info.overview_size)
  {
    unsigned article;
    article = (unsigned)
    	strtoul(&newsfind_info.overview[newsfind_info.overview_ix], 0, 0);
    ++newsfind_info.message;
    for (hdr = 0; hdr <= (int) newsfind_info.header; ++hdr)
    {
      while (newsfind_info.overview[newsfind_info.overview_ix++] != '\t');
    }
    if (findexp(&newsfind_info.overview[newsfind_info.overview_ix],
    	newsfind_info.expression, '\t'))
    {
      while (newsfind_info.overview[newsfind_info.overview_ix++] != '\n');
      nfstatus_setmessage(newsfind_info.message, newsfind_info.messages);
      findbox_found(article, findbox_searchoverview);
      return;
    }
    while (newsfind_info.overview[newsfind_info.overview_ix++] != '\n');
  }
  idle_on(findbox_nextgroup, 0);
  return;
}

static void findbox_nextarticle_backwards()
{
  int hdr;
  int ix;
  while (newsfind_info.overview_ix >= 0)
  {
    unsigned article;
    while (newsfind_info.overview_ix >= 0 &&
    	newsfind_info.overview[--newsfind_info.overview_ix] != '\n');
    ix = newsfind_info.overview_ix + 1;
    /* Make sure next iteration skips this article */
    /*--newsfind_info.overview_ix;*/
    article = (unsigned)
    	strtoul(&newsfind_info.overview[ix], 0, 0);
    ++newsfind_info.message;
    for (hdr = 0; hdr <= (int) newsfind_info.header; ++hdr)
    {
      while (newsfind_info.overview[ix++] != '\t');
    }
    if (findexp(&newsfind_info.overview[ix],
    	newsfind_info.expression, '\t'))
    {
      nfstatus_setmessage(newsfind_info.message, newsfind_info.messages);
      findbox_found(article, findbox_searchoverview);
      return;
    }
  }
  newsfind_info.overview_ix = -1;
  idle_on(findbox_nextgroup, 0);
  return;
}

static void (*findbox_nextarticle)(void);

static int findbox_searchoverview(int c, WimpPollBlock *e, IdBlock *i, void *h)
{
  if ((newsfind_info.reverse && newsfind_info.overview_ix < 0) ||
  	(!newsfind_info.reverse &&
  	newsfind_info.overview_ix >= newsfind_info.overview_size-1))
  {
    idle_on(findbox_nextgroup, 0);
    return 1;
  }
  if (newsfind_info.scope >= FB_SearchBody)
  {
    if (newsfind_info.reverse)
    {
      --newsfind_info.overview_ix;	/* Skip current \n */
      while (newsfind_info.overview_ix >= 0 &&
      	newsfind_info.overview[--newsfind_info.overview_ix] != '\n');
      ++newsfind_info.overview_ix;
      if (newsfind_info.overview_ix < 0)
      {
        idle_on(findbox_nextgroup, 0);
        return 1;
      }
    }
    newsfind_info.article_no =
    	atoi(&newsfind_info.overview[newsfind_info.overview_ix]);
    idle_on(findbox_getarticle, 0);
    if (newsfind_info.reverse)
      --newsfind_info.overview_ix;
    else
      while (newsfind_info.overview[newsfind_info.overview_ix++] != '\n');
  }
  else
    findbox_nextarticle();
  return 1;
}

static void findbox_searcharticle(int article)
{
  int aix;
  int done_headers = 0;
  for (aix = 0; aix < newsfind_info.article_size;)
  {
    if (findexp(&newsfind_info.article[aix],
    	newsfind_info.expression, '\n'))
    {
      if (newsfind_info.scope == FB_SearchBody ||
      	newsfind_info.scope == FB_SearchBodyOnly && done_headers ||
      	newsfind_info.scope == FB_SearchAllHeaders && !done_headers)
      {
        free(newsfind_info.article);
        newsfind_info.article = NULL;
        findbox_found(article, findbox_searchoverview);
        return;
      }
    }
    if (newsfind_info.article[aix] == '\n')
      done_headers = 1;
    while (newsfind_info.article[aix++] != '\n' &&
    	aix < newsfind_info.article_size);
  }
  free(newsfind_info.article);
  newsfind_info.article = NULL;
  findbox_searchoverview(0, 0, 0, 0);
}

static void findbox_gotarticle(int article, int size, const char *filename)
{
  if (size == -1)	/* Header-only, need to find real file size */
  {
    if (E(_swix(OS_File, _INR(0,1)|_OUT(4), 5, filename, &size)))
    {
      findbox_abortsearch(0, 0, 0, 0);
      return;
    }
  }
  newsfind_info.article_size = size;
  newsfind_info.article = malloc(size);
  if (!newsfind_info.article)
  {
    err_complain(0, (char *) msgs_nomem());
    findbox_abortsearch(0, 0, 0, 0);
    return;
  }
  if (E(_swix(OS_File, _INR(0,3), 255, filename, newsfind_info.article, 0)))
  {
    free(newsfind_info.article);
    newsfind_info.article = 0;
    findbox_abortsearch(0, 0, 0, 0);
    return;
  }
  findbox_searcharticle(article);
}

static void findbox_gotoverview(int scrap, const char *filename)
{
  unsigned ui;
  int otype;
  if (E(_swix(OS_File, _INR(0,1)|_OUT(0)|_OUT(4),
  	5, filename, &otype, &newsfind_info.overview_size)))
  {
    findbox_abortsearch(0, 0, 0, 0);
    return;
  }
  if (otype != 1)
  {
    err_complain(0, msgs_lookup("Overview"));
    findbox_abortsearch(0, 0, 0, 0);
    return;
  }
  if (newsfind_info.overview)
    free(newsfind_info.overview);
  newsfind_info.overview = malloc(newsfind_info.overview_size);
  if (!newsfind_info.overview)
  {
    err_complain(0, (char *) msgs_nomem());
    findbox_abortsearch(0, 0, 0, 0);
    return;
  }
  if (E(_swix(OS_File, _INR(0,3), 255, filename, newsfind_info.overview, 0)))
  {
    free(newsfind_info.overview);
    newsfind_info.overview = 0;
    findbox_abortsearch(0, 0, 0, 0);
    return;
  }
  if (scrap)	/* Delete scrap file */
    _swix(OS_File, _INR(0,1), 6, filename);
  /* Count messages */
  for (ui = newsfind_info.messages = 0;
  	ui < newsfind_info.overview_size;
  	ui++)
  {
    if (newsfind_info.overview[ui]=='\n')
      ++newsfind_info.messages;
  }
  log_group(newsfind_info.group, newsfind_info.messages);
  nfstatus_setmessages(newsfind_info.messages);
  if (newsfind_info.reverse)
    newsfind_info.overview_ix = newsfind_info.overview_size - 1;
  else
    newsfind_info.overview_ix = 0;
  findbox_searchoverview(0, 0, 0, 0);
}

static int findbox_reply(WimpMessage *m, void *h)
{
  switch (m->data.words[0])
  {
    case NewsBase_Overview:
      if (m->data.bytes[12] < 31)
        idle_on(findbox_nextgroup, 0);
      else
        findbox_gotoverview(m->data.words[1], &m->data.bytes[12]);
      return 1;
    case NewsBase_Overview+1:
    case NewsBase_Overview+2:
    case NewsBase_Overview+9:
      idle_on(findbox_nextgroup, 0);
      return 1;
    case NewsBase_GetArticle:
      if (m->data.bytes[12] < 31)
        findbox_searchoverview(0, 0, 0, 0);
      else
        findbox_gotarticle(m->data.words[1],
        	m->data.words[2], &m->data.bytes[12]);
      return 1;
    case NewsBase_GetArticle+1:
    case NewsBase_GetArticle+2:
    case NewsBase_GetArticle+9:
      findbox_searchoverview(0, 0, 0, 0);
      return 1;
    case NewsBase_CopyArticle:
      log_backspace();
      strcpyC(zak, msgs_lookup("Copied"));
      log_log(zak);
      log_newline();
      if (newsfind_info.del)
        findbox_deletearticle(del_article, del_group);
      else
        idle_on(found_idler, 0);
      return 1;
    case NewsBase_CopyArticle+1:
    case NewsBase_CopyArticle+2:
    case NewsBase_CopyArticle+9:
      idle_on(found_idler, 0);
      return 1;
    case NewsBase_DeleteArticle:
      log_backspace();
      strcpyC(zak, msgs_lookup("Deleted"));
      log_log(zak);
      log_newline();
    case NewsBase_DeleteArticle+1:
    case NewsBase_DeleteArticle+2:
    case NewsBase_DeleteArticle+9:
/*
sprintf(zak, "Action sub-code = %d\n", m->data.words[0]-NewsBase_DeleteArticle);
log_log(zak);
sprintf(zak, "[Wanted to delete article %d of group ", m->data.words[1]);
log_log(zak);
strcpyC(zak, ((NewsBaseDeleteArticle *) m)->groupname);
log_log(zak);
log_log("]\n");
*/
      idle_on(found_idler, 0);
      return 1;
  }
  return 0;
}

static int findbox_nextgroup(int c, WimpPollBlock *e, IdBlock *i, void *h)
{
  NewsBaseGetOverview nbgo;
  idle_off();
  if (!findbox_firstgroup)
    log_endgroup();
  do
  {
    if (!findbox_firstgroup)
    {
      while (*newsfind_info.group!=',' && *newsfind_info.group>31)
        ++newsfind_info.group;
      if (*newsfind_info.group == ',')
        ++newsfind_info.group;
    }
    else
      findbox_firstgroup = 0;
    if (!newsfind_info.group[0])
    {
      findbox_abortsearch(0, 0, 0, 0);
      nfstatus_complete(newsfind_info.found);
      return 1;
    }
    nbgo.first = nbgo.last = 0;
    strcpycomma(nbgo.groupname, newsfind_info.group);
  }
  while (!strincmp(nbgo.groupname, nfchoices.foundfolder, 256));
  nfstatus_setgroup(nbgo.groupname);
  newsfind_info.message = newsfind_info.messages = 0;
  nfstatus_blankmessage();
  newsbase_send(NewsBase_Overview, &nbgo);
  return 1;
}

static void findbox_sgbuilt(newsfind_groupinfo *groupinfo)
{
  if (!groupinfo->groups[0])
  {
    strncpyC(zak, msgs_lookup("NoGroups"), 256);
    err_report(0, zak);
    log_abort();
    if (findbox_id)
      E(gadget_unfade(0, findbox_id, FB_Search));
    return;
  }
  if (findbox_id && findbox_clickedselect)
    E(toolbox_hide_object(0, findbox_id));
  nfstatus_show();
  nfstatus_busy();
  newsfind_info.status = Busy;
  newsfind_info.group = groupinfo->groups;
  newsfind_info.found = 0;
  newsfind_info.overview = 0;
  if (E(event_register_message_handler(Wimp_MNewsBaseReply, findbox_reply, 0)))
    findbox_abortsearch(0, 0, 0, 0);
  else
  {
    findbox_firstgroup = 1;
    idle_on(findbox_nextgroup, 0);
  }
}

static int findbox_wild(const char *expression, const char *sequence)
{
  char *wild = strstr(expression, sequence);
  if (!wild)
    return 0;
  if (wild>expression && wild[-1]=='\\')
    return 0;
  return 1;
}

static int findbox_dosearch()
{
  static char zak2[64];
  static char zak3[128];
  int escs = 0;
  int escix;
  if (!newsfind_info.ctrl_group[0])
  {
    err_report(0, msgs_lookup("NoGroups"));
    return 0;
  }
  if (!strincmp(newsfind_info.ctrl_group, nfchoices.foundfolder, 256))
  {
    err_report(0, msgs_lookup("Clash"));
    return 0;
  }
  if (!newsfind_info.expression[0])
  {
    err_report(0, msgs_lookup("Expression"));
    return 0;
  }
  if (findbox_wild(newsfind_info.expression, "**") ||
  	findbox_wild(newsfind_info.expression, "*#"))
  {
    err_report(0, msgs_lookup("Wild"));
    return 0;
  }
  /* Not allowed odd number of esc chars at end of string */
  for (escix = strlen(newsfind_info.expression) - 1;
  	escix >= 0 && newsfind_info.expression[escix] == '\\';
  	--escix)
    escs++;
  if (escs & 1)
  {
    err_report(0, msgs_lookup("EscExp"));
    return 0;
  }
  if (!newsfind_info.strict)
  {
    int end;
    /* Sandwich between *s */
    switch (newsfind_info.expression[0])
    {
      case '#':
        newsfind_info.expression[0] = '*';
      case '*':
        break;
      default:
        memmove(newsfind_info.expression+1, newsfind_info.expression,
        	strlen(newsfind_info.expression)+1);
        newsfind_info.expression[0] = '*';
    }
    end = strlen(newsfind_info.expression);
    switch (newsfind_info.expression[end-1])
    {
      case '#':
        newsfind_info.expression[end-1] = '*';
        newsfind_info.expression[end+1] = 0;
      case '*':
        break;
      default:
        newsfind_info.expression[end] = '*';
        newsfind_info.expression[end+1] = 0;
    }
  }
  if (newsfind_info.reverse)
    findbox_nextarticle = findbox_nextarticle_backwards;
  else
    findbox_nextarticle = findbox_nextarticle_forwards;
  findbox_searchgroups.complete = findbox_sgbuilt;
  nbgroups_initialise(&findbox_searchgroups, newsfind_info.ctrl_group);
  log_start();
  strcpyC(zak, msgs_lookup("LogStart"));
  log_log(zak);
  log_time();
  strcpyC(zak, msgs_lookup("LogInfo1"));
  if (newsfind_info.scope == FB_SearchHeader)
  {
    switch (newsfind_info.header)
    {
      case FB_Subject:
        strcpy(zak2, "\"Subject");
        break;
      case FB_From:
        strcpy(zak2, "\"From");
        break;
      case FB_Date:
        strcpy(zak2, "\"Date");
        break;
      case FB_MessageId:
        strcpy(zak2, "\"Message-Id");
        break;
      case FB_References:
        strcpy(zak2, "\"References");
        break;
    }
    strcpyC(zak2 + strlen(zak2), msgs_lookup("LogHeader"));
  }
  else
    strcpyC(zak2, msgs_lookup("LogBody"));
  strncpyC(zak3, newsfind_info.ctrl_group, sizeof(zak3));
  log_log(zak, zak2, zak3);
  log_newline();
  strcpyC(zak, msgs_lookup("LogInfo2"));
  log_log(zak, newsfind_info.expression);
  log_newline();
  return 1;
}

static int findbox_startsearch(int c, ToolboxEvent *e, IdBlock *i, void *h)
{
  char groups[256];
  findbox_clickedselect = !(e->hdr.flags & 1);
  E(stringset_get_selected(0, findbox_id, FB_Group, groups, 200, 0));
  free((char *) newsfind_info.ctrl_group);
  newsfind_info.ctrl_group = strdupC(groups);
  E(writablefield_get_value(0, findbox_id, FB_Expression,
  	newsfind_info.expression, 256, 0));
  E(optionbutton_get_state(0, findbox_id, FB_Strict, &newsfind_info.strict));
  E(radiobutton_get_state(0, findbox_id, FB_SearchHeader,
  	0, (int *) &newsfind_info.scope));
  if (newsfind_info.scope == FB_SearchHeader)
    E(stringset_get_selected(StringSet_IndexedSelection,
    	findbox_id, FB_Header, (int *) &newsfind_info.header));
  E(optionbutton_get_state(0, findbox_id, FB_CaseSensve,
  	&newsfind_info.casesensve));
  E(optionbutton_get_state(0, findbox_id, FB_Reverse,
  	&newsfind_info.reverse));
  E(optionbutton_get_state(0, findbox_id, FB_Delete,
  	&newsfind_info.del));
  E(gadget_fade(0, findbox_id, FB_Search));
  if (!findbox_dosearch())
      E(gadget_unfade(0, findbox_id, FB_Search));
  return 1;
}

void findbox_initialise()
{
  EF(event_register_message_handler(Wimp_MNewsBaseUpdate, findbox_update, 0));
  EF(event_register_toolbox_handler(-1, NFT_AbortSearch,
  	findbox_abortsearch, 0));
  if (!nfchoices.extctrl)
  {
    EF(event_register_toolbox_handler(-1, NFT_ScopeChange,
    	findbox_states, 0));
    EF(event_register_toolbox_handler(-1, NFT_PerformSearch,
    	findbox_startsearch, 0));
    EF(event_register_toolbox_handler(-1, NFT_CancelSearch,
    	findbox_cancelsearch, 0));
    EF(toolbox_create_object(0, "Search", &findbox_id));

    E(stringset_set_selected(0, findbox_id, FB_Group,
    	(char *) newsfind_info.ctrl_group));
    E(optionbutton_set_state(0, findbox_id, FB_Strict, newsfind_info.strict));
    E(radiobutton_set_state(0, findbox_id, FB_SearchHeader,
    	newsfind_info.scope));
    if (newsfind_info.scope == FB_SearchHeader)
      E(stringset_set_selected(StringSet_IndexedSelection,
      	findbox_id, FB_Header, (char *) (int) newsfind_info.header));
    E(optionbutton_set_state(0, findbox_id, FB_CaseSensve,
    	newsfind_info.casesensve));
    E(optionbutton_set_state(0, findbox_id, FB_Reverse,
    	newsfind_info.reverse));

    if (!nfchoices.xpos && !nfchoices.ypos)
      EF(toolbox_show_object(0, findbox_id, Toolbox_ShowObject_Default, NULL,
      	NULL_ObjectId, NULL_ComponentId));
    else
    {
      int pos[2];
      pos[0] = nfchoices.xpos;
      pos[1] = nfchoices.ypos;
      EF(toolbox_show_object(0, findbox_id, Toolbox_ShowObject_TopLeft, pos,
      	NULL_ObjectId, NULL_ComponentId));
    }
    E(gadget_fade(0, findbox_id, FB_Group));
    findbox_groupinfo.complete = findbox_listbuilt;
    findbox_getgroups();
  }
  else
  {
    if (!findbox_dosearch())
      exit(0);
  }
}

static int findbox_cancelsearch(int c, ToolboxEvent *e, IdBlock *i, void *h)
{
  if (nfchoices.transient)
    exit(0);
  return 1;
}

static int findbox_abortsearch(int c, ToolboxEvent *e, IdBlock *i, void *h)
{
  if (newsfind_info.status == Busy)
  {
    E(event_deregister_message_handler(Wimp_MNewsBaseReply, findbox_reply, 0));
    newsfind_info.status = Idle;
  }
  nfstatus_idle();
  if (findbox_id)
    E(gadget_unfade(0, findbox_id, FB_Search));
  if (findbox_searchgroups.groups)
  {
    free(findbox_searchgroups.groups);
    findbox_searchgroups.groups = 0;
  }
  if (newsfind_info.overview)
  {
    free(newsfind_info.overview);
    newsfind_info.overview = 0;
  }
  if (newsfind_info.article)
  {
    free(newsfind_info.article);
    newsfind_info.article = 0;
  }
  idle_off();
  if (c)
    log_abort();
  else
    log_complete(newsfind_info.found);
  if (nfchoices.transient)
    exit(0);
  return 1;
}
